package com.jinsp.hotfix;

import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
import android.os.Build;
import android.text.TextUtils;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import dalvik.system.DexFile;
import dalvik.system.PathClassLoader;

/**
 * Created by luoshuai on 1/5/22
 * <p>
 * 修复代码bug功能，采用类替换原理，最低Android 5.1开始
 */
public class CodeFixManager {

    /**
     * 修复代码
     *
     * @param context     上下文
     * @param fixFilePath 修复代码文件路径
     */
    public static void codeFix(Context context, String fixFilePath) {
        if (context != null && !TextUtils.isEmpty(fixFilePath)) {
            Context applicationContext = context.getApplicationContext();
            File fixFile = new File(fixFilePath);
            if (!fixFile.exists()) {
                return;
            }
            File[] allFixFile = fixFile.listFiles();
            if (allFixFile == null || allFixFile.length == 0) {
                return;
            }
            //dex优化文件夹
            File optimizedDirectory = new File(applicationContext.getCacheDir().getAbsolutePath(), "optimized_fix");
            if (!optimizedDirectory.exists()) {
                optimizedDirectory.mkdirs();
            }
            //获取应用类加载器
            PathClassLoader pathClassLoader = (PathClassLoader) applicationContext.getClassLoader();
            //获取DexPathList
            Class<?> baseDexClassLoader = pathClassLoader.getClass().getSuperclass();
            try {
                Field pathListField = baseDexClassLoader.getDeclaredField("pathList");
                if (pathListField != null) {
                    pathListField.setAccessible(true);
                    Object dexPathList = pathListField.get(pathClassLoader);
                    //获取Element数组
                    if (dexPathList != null) {
                        Class<?> dexPathListClass = dexPathList.getClass();
                        Field dexElementsField = dexPathListClass.getDeclaredField("dexElements");
                        if (dexElementsField != null) {
                            dexElementsField.setAccessible(true);
                            Object[] dexElements = (Object[]) dexElementsField.get(dexPathList);
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                                //7.0以上替换类加载器取消混合编译
                                replacePathClassLoader(applicationContext, dexPathListClass, dexPathList,
                                        dexElements, allFixFile);
                            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
                                    && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                                //5.1-6.0
                                ArrayList<IOException> ioExceptionArrayList = new ArrayList<>();
                                ArrayList<File> fixFileList = new ArrayList<>(Arrays.asList(allFixFile));
                                Method makeDexElementsMethod = dexPathListClass.getDeclaredMethod("makeDexElements", ArrayList.class,
                                        File.class, ArrayList.class);
                                if (makeDexElementsMethod != null) {
                                    makeDexElementsMethod.setAccessible(true);
                                    Object[] fixDexElements = (Object[]) makeDexElementsMethod.invoke(null, fixFileList,
                                            optimizedDirectory, ioExceptionArrayList);
                                    //重新赋值dexElements
                                    mergeAndUpdateElementArray(dexElements, fixDexElements,
                                            dexElementsField, dexPathList);
                                }
                            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                                //6.0-7.0
                                List<IOException> ioExceptionArrayList = new ArrayList<>();
                                List<File> fixFileList = new ArrayList<>(Arrays.asList(allFixFile));
                                Method makeDexElementsMethod = dexPathListClass.getDeclaredMethod("makePathElements", List.class,
                                        File.class, List.class);
                                if (makeDexElementsMethod != null) {
                                    makeDexElementsMethod.setAccessible(true);
                                    Object[] fixDexElements = (Object[]) makeDexElementsMethod.invoke(null, fixFileList,
                                            optimizedDirectory, ioExceptionArrayList);
                                    mergeAndUpdateElementArray(dexElements, fixDexElements,
                                            dexElementsField, dexPathList);
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 取消7.0以上的混合编译功能
     *
     * @param context          上下文
     * @param dexPathListClass DexPathList类
     * @param dexPathList      DexPathList实例
     * @param dexElements      dexElements数组
     * @param allFixFile       修复的全部文件
     */
    private static void replacePathClassLoader(Context context, Class<?> dexPathListClass,
                                               Object dexPathList, Object[] dexElements,
                                               File[] allFixFile) throws Exception {
        if (dexElements == null || dexElements.length == 0) {
            return;
        }
        //拼接修复文件 :分割
        StringBuilder dexFileBuilder = new StringBuilder();
        boolean flag = true;
        String packageName = context.getPackageName();
        String allDexFilePath = null;
        String allNativeLibraryPath = null;
        for (File file : allFixFile) {
            if (flag) {
                flag = false;
            } else {
                dexFileBuilder.append(File.pathSeparator);
            }
            dexFileBuilder.append(file.getAbsolutePath());
        }
        //获取Element中的dexFile并拼接完整的dex
        Object dexElement = dexElements[0];
        Class<?> elementClass = dexElement.getClass();
        Field dexFileFiled = elementClass.getDeclaredField("dexFile");
        if (dexFileFiled != null) {
            dexFileFiled.setAccessible(true);
            for (Object element : dexElements) {
                DexFile dexFile = (DexFile) dexFileFiled.get(element);
                String dexFileName = null;
                if (dexFile != null) {
                    dexFileName = dexFile.getName();
                }
                if (TextUtils.isEmpty(dexFileName) || !dexFileName.contains(File.separator + packageName)) {
                    continue;
                }
                if (flag) {
                    flag = false;
                } else {
                    dexFileBuilder.append(File.pathSeparator);
                }
                dexFileBuilder.append(dexFileName);
            }
            allDexFilePath = dexFileBuilder.toString();
            //获取so加载路径
            Field nativeLibraryDirectoriesField = dexPathListClass.getDeclaredField("nativeLibraryDirectories");
            if (nativeLibraryDirectoriesField != null) {
                nativeLibraryDirectoriesField.setAccessible(true);
                List<File> nativeLibraryDirectories = (List<File>) nativeLibraryDirectoriesField.get(dexPathList);
                if (nativeLibraryDirectories != null) {
                    StringBuilder nativeLibraryBuilder = new StringBuilder();
                    flag = true;
                    for (File nativeLibraryDirectory : nativeLibraryDirectories) {
                        if (nativeLibraryDirectory != null) {
                            if (flag) {
                                flag = false;
                            } else {
                                nativeLibraryBuilder.append(File.pathSeparator);
                            }
                            nativeLibraryBuilder.append(nativeLibraryDirectory.getAbsolutePath());
                        }
                    }
                    allNativeLibraryPath = nativeLibraryBuilder.toString();
                }
            }
        }
        if (!TextUtils.isEmpty(allDexFilePath)) {
            //创建新的应用类加载器
            ClassLoader newClassLoader = new CodeFixClassLoader(allDexFilePath, allNativeLibraryPath,
                    ClassLoader.getSystemClassLoader());
            //替换系统内使用的应用类加载器
            Thread.currentThread().setContextClassLoader(newClassLoader);
            Class<ContextWrapper> contextWrapperClass = ContextWrapper.class;
            //替换ContentWrapper中的Context
            Field mBaseField = contextWrapperClass.getDeclaredField("mBase");
            if (mBaseField != null) {
                mBaseField.setAccessible(true);
                Context mBase = (Context) mBaseField.get(context);
                if (mBase != null) {
                    Class<? extends Context> contextImplClass = mBase.getClass();
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                        //8.0以上替换ContextImpl中的ClassLoader
                        Field mClassLoaderField = contextImplClass.getDeclaredField("mClassLoader");
                        if (mClassLoaderField != null) {
                            mClassLoaderField.setAccessible(true);
                            mClassLoaderField.set(mBase, newClassLoader);
                        }
                    }
                    //替换ContextImpl中的LoadedApk中的ClassLoader
                    Field mPackageInfoField = contextImplClass.getDeclaredField("mPackageInfo");
                    if (mPackageInfoField != null) {
                        mPackageInfoField.setAccessible(true);
                        Object loadedApk = mPackageInfoField.get(mBase);
                        if (loadedApk != null) {
                            Field mLoadedApkClassLoaderField = loadedApk.getClass().getDeclaredField("mClassLoader");
                            if (mLoadedApkClassLoaderField != null) {
                                mLoadedApkClassLoaderField.setAccessible(true);
                                mLoadedApkClassLoaderField.set(loadedApk, newClassLoader);
                            }
                        }
                    }
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O_MR1) {
                        //8.1之前替换Resources中的ClassLoader
                        Resources resources = context.getResources();
                        Class<Resources> resourcesClass = Resources.class;
                        Field mResourcesClassLoaderField = resourcesClass.getDeclaredField("mClassLoader");
                        if (mResourcesClassLoaderField != null) {
                            mResourcesClassLoaderField.setAccessible(true);
                            mResourcesClassLoaderField.set(resources, newClassLoader);
                        }
                        //替换DrawableInflater中的ClassLoader
                        Field mDrawableInflaterField = resourcesClass.getDeclaredField("mDrawableInflater");
                        if (mDrawableInflaterField != null) {
                            mDrawableInflaterField.setAccessible(true);
                            Object drawableInflater = mDrawableInflaterField.get(resources);
                            if (drawableInflater != null) {
                                Field mDrawableInflaterClassLoaderField = drawableInflater.getClass().getDeclaredField("mClassLoader");
                                if (mDrawableInflaterClassLoaderField != null) {
                                    mDrawableInflaterClassLoaderField.setAccessible(true);
                                    mDrawableInflaterClassLoaderField.set(drawableInflater, newClassLoader);
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 合并DexElements数组并重新赋值
     *
     * @param appDexElements   app内解析的DexElement数组
     * @param fixDexElements   修复文件解析的DexElement数组
     * @param dexElementsField dexElements
     * @param dexPathList      当前赋值的对象
     */
    private static void mergeAndUpdateElementArray(Object[] appDexElements, Object[] fixDexElements,
                                                   Field dexElementsField, Object dexPathList) throws IllegalAccessException {
        if (appDexElements != null && fixDexElements != null) {
            Object[] newDexElements = new Object[appDexElements.length + fixDexElements.length];
            System.arraycopy(fixDexElements, 0, newDexElements, 0, fixDexElements.length);
            System.arraycopy(appDexElements, 0, newDexElements, fixDexElements.length, appDexElements.length);
            dexElementsField.set(dexPathList, newDexElements);
        }
    }
}
